/** 
* All Rights Reserved , Copyright (C) 2021 , 青岛鼎信通讯股份有限公司
* 
* FileManageService
* 文件管理
* 
* 修改纪录
* 2021-03-13 版本：1.0 donghang 创建。
* @version 版本：1.0
* @author 作者：donghang
* 创建日期：2021-03-13
*/
package com.topscomm.center.service.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.topscomm.basic.BasicEntity;
import com.topscomm.basic.exception.ServiceException;
import com.topscomm.center.pojo.FileBlockRecordEntity;
import com.topscomm.center.pojo.FileManageEntity;
import com.topscomm.center.service.IFileManageService;
import com.topscomm.center.service.impl.auto.FileManageServiceAuto;
import com.topscomm.center.util.FileCenterUtil;
import com.topscomm.core.FileStoreTypeEnum;
import com.topscomm.core.factory.FileStoreFactoryService;
import com.topscomm.file.FileInfoDTO;
import com.topscomm.file.FileProperties;
import com.topscomm.file.FileSystemConst.FileProcessProgressType;
import com.topscomm.file.FileSystemConst.FileUploadStateEnum;
import com.topscomm.file.mq.FileProcessProducer;
import com.topscomm.file.task.FileProcessProgressCache;
import com.topscomm.file.task.FileProcessTaskManage;
import com.topscomm.file.util.Utils;
import com.topscomm.pub.util.ConvertUtil;
import com.topscomm.pub.util.StringBufferProxy;
import com.topscomm.tap.common.ParameterCache;

import lombok.extern.slf4j.Slf4j;

@Service(value = "fileManageService")
@Scope(value = "singleton")
@Slf4j
public class FileManageService extends FileManageServiceAuto implements IFileManageService {
	/**
	 * 文件块服务
	 */
	@Autowired
	private FileBlockRecordService fileBlockRecordService;
	/**
	 * 文件属性
	 */
	@Autowired
	private FileProperties fileProperties;
	/**
	 * 文件处理消息生产
	 */
	@Autowired
	private FileProcessProducer fileProcessProducer;
	/**
	 * 文件存储服务工厂
	 */
	@Autowired
	private FileStoreFactoryService fileStoreFactoryService;
	@Autowired
	private RedisTemplate<String, Object> redisTemplate;
	@Autowired
	private FileProcessTaskManage fileProcessTaskManage;

	/**
	 * 上传文件,uuid作为唯一索引
	 * 
	 * @description:
	 * @param fileBytes
	 * @param dataMap
	 * @author: donghang
	 * @throws IOException
	 * @date: 2021年3月14日下午10:32:03
	 * @modify:
	 */
	@Transactional(rollbackFor = Exception.class)
	public String uploadFile(File file, Map<String, Object> dataMap) throws IOException {
		// 1.上传文件
		String fileDirPath = Utils.buildFilePath("");
		String fileName = ConvertUtil.convertToString(file.getName());
		String uuid = UUID.randomUUID().toString();
		StringBufferProxy newFileName = new StringBufferProxy();
		newFileName.appendSingle("{0}.{1}", uuid, Utils.getExtension(fileName));
		FileInfoDTO fileInfoDTO = new FileInfoDTO();
		fileInfoDTO.setFileDirPath(fileDirPath);
		fileInfoDTO.setFileName(newFileName.toString());
		fileInfoDTO.setBucketName(ConvertUtil.convertToString(dataMap.get("bucketname")));
		fileInfoDTO.setFileId(uuid);
		fileInfoDTO.setFileSize(file.length());
		fileInfoDTO.setBlockSize(file.length());
		InputStream inputStream = null;
		try {
			inputStream = new FileInputStream(file);
			this.fileStoreFactoryService.upload(inputStream, fileInfoDTO);
		} catch (Exception e) {
			throw new ServiceException("上传失败，请重新上传");
		} finally {
			if (inputStream != null) {
				inputStream.close();
			}
		}
		// 2.文件实体
		dataMap.put("dirpath", fileDirPath);
		dataMap.put("identifier", uuid);
		dataMap.put("location", newFileName);
		FileManageEntity fileManageEntity = this.createEntity(dataMap);
		fileManageEntity.setUploadstate(FileUploadStateEnum.Finish);
		fileManageEntity = this.insert(fileManageEntity);
		return uuid;
	}

	/**
	 * 上传文件块(控制同一个来源下的文件相同即可)
	 * 
	 * @description:
	 * @param fileBytes
	 * @param sourcetype
	 * @param sourceid
	 * @param blockEntity
	 * @return
	 * @author: donghang
	 * @date: 2020年8月22日下午5:57:34
	 * @modify:
	 */
	@Transactional(rollbackFor = Exception.class)
	public void uploadFileBlock(InputStream inputStream, Map<String, Object> dataMap) {
		// 1.上传文件块，构建路径，年月日
		String fileDirPath = Utils.buildFilePath(ConvertUtil.convertToString(dataMap.get("identifier")));
		dataMap.put("filedirpath", fileDirPath);
		this.fileBlockRecordService.uploadFileBlock(inputStream, dataMap);
		StringBufferProxy sql = new StringBufferProxy();
		// 2.判断是否存在文件记录,新增文件记录
		sql.clear();
		sql.appendSingle(" identifier='{0}'", ConvertUtil.convertToString(dataMap.get("identifier")));
		// 已经上传，则更新
		List<Map<String, Object>> fileMapList = this.queryMapByWhere(sql.toString());
		if (fileMapList.size() > 0) {
			sql.clear();
			sql.appendSingle(" 1=1 ");
			sql.appendSingle(" and identifier = '{0}' ", ConvertUtil.convertToString(dataMap.get("identifier")));
			Map<String, Object> updataMap = new HashMap<String, Object>();
			updataMap.put("name", ConvertUtil.convertToString(dataMap.get("filename")));
			updataMap.put("usercode", ConvertUtil.convertToString(dataMap.get("usercode")));
			updataMap.put("username", ConvertUtil.convertToString(dataMap.get("username")));
			updataMap.put("recordtime", new Date());
			this.updateByWhere(updataMap, sql.toString());
		}
		// 第一次上传新增记录
		if (fileMapList.size() == 0 && ConvertUtil.convertToInt(dataMap.get("blocknum")) == 1) {
			// 新增文件记录
			dataMap.put("dirpath", fileDirPath);
			dataMap.put("location", ConvertUtil.convertToString(dataMap.get("identifier")) + "."
					+ ConvertUtil.convertToString(dataMap.get("filetype")));
			FileManageEntity fileManageEntity = this.createEntity(dataMap);
			fileManageEntity.setUploadstate(FileUploadStateEnum.Uploading);
			fileManageEntity = this.insert(fileManageEntity);
		}

	}

	/**
	 * 处理合并文件，不能进行事务控制，因为处理时间比较长，加事务，容易引起行级锁
	 * 
	 * @description:
	 * @param inputStream
	 * @param dataMap
	 * @author: donghang
	 * @date: 2021年4月20日下午10:17:29
	 * @modify:
	 */
	public boolean mergeFileBlocks(FileManageEntity fileManageEntity) {
		// 查询文件块
		StringBufferProxy sql = new StringBufferProxy();
		sql.appendSingle(" 1=1 ");
		sql.appendSingle(" and identifier = '{0}' ", fileManageEntity.getIdentifier());
		List<FileManageEntity> fileList = this.queryByWhere(sql.toString());
		if (fileList.size() == 0) {
			throw new ServiceException("上传失败，请重新上传");
		} else {
			FileManageEntity finishFileManageEntity = null;
			for (FileManageEntity fileEntity : fileList) {
				String fileState = ConvertUtil.convertToString(fileEntity.getUploadstate());
				if (fileState.equals(FileUploadStateEnum.Finish)) {
					finishFileManageEntity = fileEntity;
					break;
				}
			}
			// 已经上传完成，不需要处理
			if (finishFileManageEntity != null) {
				return true;
			}
			if (FileUploadStateEnum.Uploading.equals(ConvertUtil.convertToString(fileList.get(0).getUploadstate()))) {
				// 上传没有完成才进行合并
				this.fileMergeProcess(fileList.get(0));
			}
			// 标识文件上传完成
			Map<String, Object> dataMap = new HashMap<String, Object>();
			dataMap.put("uploadstate", FileUploadStateEnum.UploadFinish);
			this.updateByWhere(dataMap, sql.toString());
			// 根据不同文件类型，进行后续的文件转换处理
			return this.fileConvertProcess(fileList.get(0));
		}

	}

	/**
	 * 通知文件存储服务器进行合并文件处理
	 * 
	 * @description:
	 * @param fileManageEntity
	 * @author: donghang
	 * @date: 2021年5月13日上午11:08:55
	 * @modify:
	 */
	private void fileMergeProcess(FileManageEntity fileManageEntity) {
		// 上传没有完成才进行合并
		try {
			FileInfoDTO fileInfoDTO = FileCenterUtil.fileEntityConvertToFileServiceDto(fileManageEntity,
					fileManageEntity.getIdentifier() + "." + fileManageEntity.getFiletype());
			// 得到预签名的信息
			Map<String, Object> presignedMap = (Map<String, Object>) this.redisTemplate.opsForValue()
					.get(fileManageEntity.getIdentifier());
			if (presignedMap == null) {
				throw new ServiceException("上传失败，请重新上传");
			}
			// 通知文件服务器进行文件合并操作
			this.fileStoreFactoryService.mergeMultipartUpload(fileInfoDTO,
					ConvertUtil.convertToString(presignedMap.get("uploadId")));
		} catch (Exception e) {
			// 合并失败，则清空进行重新上传
			// this.deleteFileByIdentifier(fileManageEntity.getIdentifier());
			// 清空预签名
			// this.redisTemplate.delete(fileList.get(0).getIdentifier());
			throw new ServiceException("上传失败，请重新上传");
		}
	}

	/**
	 * 
	 * @description:
	 * @author: donghang
	 * @date: 2021年5月13日上午11:11:25
	 * @modify:
	 */
	private boolean fileConvertProcess(FileManageEntity fileManageEntity) {
		String documntProcess = ParameterCache.getValue("FileProcessFormat", "ppt,pptx,doc,docx,xls,xlsx,pdf");
		String videoFormater = ParameterCache.getValue("FileVideoFile", "avi,mov,rmvb,rm,flv,mp4,3gp");
		String fileType = ConvertUtil.convertToString(fileManageEntity.getFiletype().toLowerCase());
		Map<String, Object> dataMap = new HashMap<String, Object>();
		if (documntProcess.contains(fileType) || videoFormater.contains(fileType)) {
			// 通过责任链进行文件的处理
			Map<String, Object> messageMap = new HashMap<String, Object>();
			messageMap.put("identifier", fileManageEntity.getIdentifier());
			messageMap.put("filename", ConvertUtil.convertToString(
					fileManageEntity.getIdentifier() + "." + Utils.getExtension(fileManageEntity.getName())));
			messageMap.put("foldername", fileManageEntity.getDirpath());
			messageMap.put("bucketname", fileManageEntity.getBucketname());
			messageMap.put("storagetype", fileManageEntity.getStoragetype());
			// 异步提交线程处理文件
			this.fileProcessTaskManage.handle(messageMap);
			return false;
		} else {
			// 更新文件记录
			dataMap.put("uploadstate", FileUploadStateEnum.Finish);
			StringBufferProxy sql = new StringBufferProxy();
			sql.appendSingle(" 1=1 ");
			sql.appendSingle(" and identifier = '{0}' ", fileManageEntity.getIdentifier());
			this.updateByWhere(dataMap, sql.toString());
			return true;
		}

	}

	/**
	 * 文件处理完成
	 * 
	 * @description:
	 * @author: donghang
	 * @date: 2021年5月13日下午5:54:23
	 * @modify:
	 */
	public void fileProcessFinish(Map<String, Object> fileMap) {
		// 保存封面
		this.saveFileTitlePage(fileMap);
		// 更新文件记录为完成
		Map<String, Object> dataMap = new HashMap<String, Object>();
		dataMap.put("uploadstate", FileUploadStateEnum.Finish);
		StringBufferProxy sql = new StringBufferProxy();
		sql.appendSingle(" 1=1 ");
		sql.appendSingle(" and identifier = '{0}' ", ConvertUtil.convertToString(fileMap.get("identifier")));
		this.updateByWhere(dataMap, sql.toString());
		// 文件处理完成
		FileProcessProgressCache.putValue(ConvertUtil.convertToString(fileMap.get("identifier")), 0,
				FileProcessProgressType.Finish, "");
		// 任务管理中删除文件
		this.fileProcessTaskManage.deleteFile(fileMap);
	}

	/**
	 * 文件处理失败
	 * 
	 * @description:
	 * @param fileMap
	 * @author: donghang
	 * @date: 2021年5月14日下午11:17:28
	 * @modify:
	 */
	public void fileProcessError(Map<String, Object> fileMap, String errorReason) {
		// 删除文件，重新开始
		StringBuilder localFileDirPath = new StringBuilder();
		localFileDirPath.append(fileProperties.getStorePath()).append("/")
				.append(ConvertUtil.convertToString(fileMap.get("identifier")));
		com.topscomm.pub.util.FileUtil.deleteDirectory(localFileDirPath.toString(), true);
		// 处理进度设置为处理失败
		FileProcessProgressCache.putValue(ConvertUtil.convertToString(fileMap.get("identifier")), 0,
				FileProcessProgressType.Error, errorReason);
		// 任务管理中删除文件
		this.fileProcessTaskManage.deleteFile(fileMap);
	}

	/**
	 * 存库文件封面，得到文件封面
	 * 
	 * @description:
	 * @param dataMap
	 * @author: donghang
	 * @date: 2021年4月6日上午9:56:51
	 * @modify:
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void saveFileTitlePage(Map<String, Object> dataMap) {
		try {
			// 封面文件夹路径
			StringBuilder localFileDirPath = new StringBuilder();
			localFileDirPath.append(fileProperties.getStorePath()).append("/")
					.append(ConvertUtil.convertToString(dataMap.get("identifier"))).append("/titlepages");
			File targetDir = new File(localFileDirPath.toString());
			// 遍历path下的文件和目录，放在File数组中
			File[] files = targetDir.listFiles();
			int maxTitlePages = ParameterCache.getIntValue("FileMaxTitlePages", 6);
			int titilPages = files.length > maxTitlePages ? maxTitlePages : files.length;
			int mod = files.length / titilPages;
			List<BasicEntity> fileManageList = new ArrayList<>();
			for (int j = 0; j < titilPages; j++) {
				// 上传到文件存储服务器,间隔取图片
				int index = j + mod * j;
				if (index >= files.length) {
					index = files.length - 1;
				}
				FileInfoDTO fileInfo = new FileInfoDTO();
				fileInfo.setBucketName(ConvertUtil.convertToString(dataMap.get("bucketname")));
				fileInfo.setFileDirPath(ConvertUtil.convertToString(dataMap.get("foldername")));
				fileInfo.setFileName(files[index].getName());
				fileInfo.setLocateFilePath(files[index].getPath());
				String uuid = UUID.randomUUID().toString();
				fileInfo.setFileId(uuid);
				this.fileStoreFactoryService.upload(fileInfo);
				FileManageEntity fileManageEntity = new FileManageEntity();
				fileManageEntity.setBucketname(ConvertUtil.convertToString(dataMap.get("bucketname")));
				fileManageEntity.setDirpath(ConvertUtil.convertToString(dataMap.get("foldername")));
				fileManageEntity.setStoragetype(FileStoreTypeEnum.MINIO.protocol);
				fileManageEntity.setUploadstate(FileUploadStateEnum.Finish);
				fileManageEntity.setIdentifier(uuid);
				fileManageEntity.setLocation(files[index].getName());
				fileManageEntity.setFiletype(Utils.getExtension(files[index].getName()));
				fileManageEntity.setSourcetype(FileManageEntity.tableName);
				fileManageEntity.setSourceid(ConvertUtil.convertToString(dataMap.get("identifier")));
				fileManageEntity.setName(files[index].getName());
				fileManageList.add(fileManageEntity);
			}
			this.insertBatch(fileManageList);
			// 删除
			com.topscomm.pub.util.FileUtil.deleteDirectory(targetDir.getParent(), true);
		} catch (Exception e) {
			log.error("处理封面异常:" + ConvertUtil.convertToString(dataMap.get("identifier")) + e.getMessage());
		}

	}

	/**
	 * 检查上传的文件块号
	 * 
	 * @description:
	 * @param fileManageEntity
	 * @return
	 * @author: donghang
	 * @date: 2020年8月20日下午11:31:37
	 * @modify:
	 */
	public Map<String, Map<String, Object>> getUploadBlocks(Map<String, Object> paraMap) {
		// 查询文件所有已上传的块
		StringBufferProxy sql = new StringBufferProxy();
		sql.appendSingle(" 1=1 ");
		sql.appendSingle(" and identifier in ('{0}') order by blocknum asc ", paraMap.get("qry_identifiers"));
		List<FileBlockRecordEntity> fileBlockRecordEntityList = fileBlockRecordService.queryByWhere(sql.toString());
		Map<String, Map<String, Object>> fileUploadBlocks = new HashMap<String, Map<String, Object>>();
		fileBlockRecordEntityList.forEach(fileBlockRecordEntity -> {
			int blockNum = ConvertUtil.convertToInt(fileBlockRecordEntity.getBlocknum());
			String identifier = ConvertUtil.convertToString(fileBlockRecordEntity.getIdentifier());
			if (fileUploadBlocks.containsKey(identifier)) {
				Map<String, Object> uploadBlockMap = fileUploadBlocks.get(identifier);
				List<Integer> alreadyUploadBlockList = (List<Integer>) uploadBlockMap.get("uploadblocklist");
				if (!alreadyUploadBlockList.contains(blockNum))
					alreadyUploadBlockList.add(blockNum);
			} else {
				Map<String, Object> uploadBlockMap = new HashMap<String, Object>();
				List<Integer> alreadyUploadBlockList = new ArrayList<Integer>();
				alreadyUploadBlockList.add(blockNum);
				uploadBlockMap.put("blocksize", fileBlockRecordEntity.getBlocksize());
				uploadBlockMap.put("uploadblocklist", alreadyUploadBlockList);
				fileUploadBlocks.put(identifier, uploadBlockMap);
			}
		});
		return fileUploadBlocks;
	}

	/**
	 * 验证上传块的有效性，主要验证redis分块上传的url是否存在
	 * 
	 * @description:
	 * @author: donghang
	 * @date: 2021年4月20日下午7:05:41
	 * @modify:
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void processUploadBlocksValid(Map<String, Map<String, Object>> fileUploadBlocks) {
		StringBufferProxy sql = new StringBufferProxy();
		for (Map.Entry<String, Map<String, Object>> entry : fileUploadBlocks.entrySet()) {
			String identifier = entry.getKey();
			List<Integer> alreadyUploadBlockList = (List<Integer>) entry.getValue().get("uploadblocklist");
			if (alreadyUploadBlockList.size() > 0) {
				// 历史上传过
				sql.clear();
				sql.appendSingle("  identifier ='{0}' ", identifier);
				List<FileManageEntity> fileList = this.queryByWhere(sql.toString());
				FileManageEntity finishFileManageEntity = null;
				for (FileManageEntity fileManageEntity : fileList) {
					String fileState = ConvertUtil.convertToString(fileManageEntity.getUploadstate());
					if (!fileState.equals(FileUploadStateEnum.Uploading)) {
						finishFileManageEntity = fileManageEntity;
						break;
					}
				}
				if (finishFileManageEntity == null) {
					// 没有完成的判断是否存在有效的预签名url
					Object presignedObject = this.redisTemplate.opsForValue().get(identifier);
					// 预签名失效，则清空已上传的，重新上传
					if (presignedObject == null) {
						// 删除已上传的文件块，重新上传
						this.deleteFileByIdentifier(identifier);
						// 重新开始上传
						alreadyUploadBlockList.clear();
					}
				}
			}
		}
	}

	/**
	 * 删除记录和块记录
	 * 
	 * @description:
	 * @param paraMap
	 * @author: donghang
	 * @date: 2020年9月16日下午6:45:48
	 * @modify:
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void deleteFile(Map<String, Object> paraMap) {
		String[] ids = ConvertUtil.convertToString(paraMap.get("ids")).split(",");
		// 删除块记录
		for (String id : ids) {
			FileManageEntity fileManageEntity = this.queryById(id);
			StringBufferProxy sql = new StringBufferProxy();
			// 1.块记录删除
			sql.clear();
			sql.appendSingle(" identifier='{0}' ", fileManageEntity.getIdentifier());
			this.fileBlockRecordService.deleteByWhere(sql.toString());
			// 2.文件记录删除
			this.deleteById(id);
		}
	}

	/**
	 * 
	 * @description:
	 * @param paraMap
	 * @author: donghang
	 * @date: 2021年4月20日下午8:11:20
	 * @modify:
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void deleteFileByIdentifier(String identifier) {
		StringBufferProxy sql = new StringBufferProxy();
		sql.appendSingle("  identifier ='{0}' ", identifier);
		this.deleteByWhere(sql.toString());
		this.fileBlockRecordService.deleteByWhere(sql.toString());
	}

	/**
	 * 
	 * @description:
	 * @return
	 * @author: donghang
	 * @date: 2021年3月30日下午5:32:30
	 * @modify:
	 */
	private FileManageEntity createEntity(Map<String, Object> dataMap) {
		FileManageEntity fileManageEntity = new FileManageEntity();
		fileManageEntity.setIdentifier(ConvertUtil.convertToString(dataMap.get("identifier")));
		fileManageEntity.setFilemd5(ConvertUtil.convertToString(dataMap.get("filemd5")));
		fileManageEntity.setName(ConvertUtil.convertToString(dataMap.get("filename")));
		fileManageEntity.setSourcesys(ConvertUtil.convertToString(dataMap.get("sourcesys")));
		fileManageEntity.setSourceid(ConvertUtil.convertToString(dataMap.get("sourceid")));
		fileManageEntity.setSourcetype(ConvertUtil.convertToString(dataMap.get("sourcetype")));
		fileManageEntity.setTotalblockcount(ConvertUtil.convertToInt(dataMap.get("totalblockcount")));
		fileManageEntity.setUsercode(ConvertUtil.convertToString(dataMap.get("usercode")));
		fileManageEntity.setUsername(ConvertUtil.convertToString(dataMap.get("username")));
		fileManageEntity.setFilesize(ConvertUtil.convertToLong(dataMap.get("filesize")));
		fileManageEntity.setBlocksize(ConvertUtil.convertToLong(dataMap.get("blocksize")));
		fileManageEntity.setDirpath(ConvertUtil.convertToString(dataMap.get("dirpath")));
		fileManageEntity.setFiletype(ConvertUtil.convertToString(dataMap.get("filetype")));
		fileManageEntity.setUploadstate(FileUploadStateEnum.UploadFinish);
		fileManageEntity.setBucketname(ConvertUtil.convertToString(dataMap.get("bucketname")));
		fileManageEntity.setStoragetype(FileStoreTypeEnum.MINIO.protocol);
		fileManageEntity.setLocation(ConvertUtil.convertToString(dataMap.get("location")));
		return fileManageEntity;
	}

}
